Stamp per-worker-process execution metadata on test results - #420
Conversation
Adds three fields to each recorded result, stamped in the process that ran the test (worker-side, before any DRb send in embedding environments): - parallel_worker_pid: Process.pid at result creation - parallel_worker_test_index: 0-based per-process monotonic counter, incremented per execution (requeued runs get their own index), fork-safe - parallel_worker_id: injected by the embedding environment via Minitest::Queue.parallel_worker_id= or CI_QUEUE_PARALLEL_WORKER_ID; nil when not applicable Stamping is first-writer-wins: embedders that run tests in forked workers and transport results to a central reporting process (e.g. Rails parallel testing over DRb, where handle_test_result runs server-side) must call Minitest::Queue.stamp_parallel_worker_metadata in the worker before sending; pre-stamped results pass through reporting untouched. Otherwise the reporting-side stamp would carry the server's pid and an arrival-order index interleaved across workers. The fields are carried nil-safely through TestData#to_h into log/test_data.json (TestDataReporter unchanged), so per-worker-process execution order is reconstructable downstream: PARTITION BY parallel_worker_id, parallel_worker_pid ORDER BY parallel_worker_test_index Assisted-By: devx/77f6d45d-84ba-4c9e-983d-5ec948229a08
|
From River: Reviewed at Blocking-ish1. Thread-based parallelization silently produces wrong dataAll three pieces of state — Under thread-based parallelization (Rails PARTITION BY job_id, parallel_worker_id, parallel_worker_pid
ORDER BY parallel_worker_test_indexthen collapses to a single partition, and the indexes interleave across threads. The result still looks like a valid per-worker execution order downstream, which is the worst failure mode for a telemetry field — silently wrong beats obviously missing. Two ways out, either is fine:
2. The version bump doesn't belong in this PRCommit Worth fixing3.
|
- Guard stamp state with a mutex: results recorded from multiple threads (e.g. a DRb server dispatching each call on its own thread) get unique, gap-free per-process indexes. Documented that per-worker order reconstruction is only meaningful with forked workers; under thread-based parallelization all threads share one partition. - Memoize the CI_QUEUE_PARALLEL_WORKER_ID env lookup per process (keyed on pid, so forked workers re-read it) instead of re-parsing on every test. - README: moved the section under Minitest, fixed stamp-timing wording, documented the relationship to ci-queue's own --worker/worker_id, and where build/job identity is expected to come from. - Tests: stamp now returns the result or nil so skip paths are assertable; added thread-safety and env-memoization tests; reset all stamp state ivars in setup/teardown; style fix in the integration assertions. Assisted-By: devx/d62b5d38-ac27-42b3-8627-64db4c652781
3384eab to
c6d40a8
Compare
|
Thanks for the thorough review — addressed in c6d40a8 (and a force-push dropping the version bump). Point by point: 1. Thread-based parallelization — Agreed, and it's actually nearer-term than 2. Version bump — Fair, that's the documented release flow and the #419 precedent. Dropped from this PR; the bump is parked on a branch and will go up as a standalone PR once this merges. 3. ENV re-read per test — Fixed. Memoized keyed on 4. Payload self-sufficiency — Documented rather than emitted. 5. Naming — Keeping Nits — All taken: section moved under Minitest (kept its own small env table — the env tables are per-feature in this README), stamp-timing wording fixed, CI — the missing runs were the GitHub Actions outage earlier today; the force-push should re-trigger. AI generated (pi/claude, reviewed by @aiden before posting) |
What
Stamps three per-worker-process execution metadata fields on every minitest result, carried nil-safely through
TestData#to_hintolog/test_data.json:parallel_worker_pid—Process.pidat result creation.parallel_worker_test_index— 0-based per-process monotonic counter, incremented per execution (requeued runs get their own index). Fork-safe: restarts when the pid changes, so each process incarnation gets a clean0,1,2,…sequence.parallel_worker_id— identifier injected by the embedding environment (e.g. a Rails parallel-testing worker number) viaMinitest::Queue.parallel_worker_id=orCI_QUEUE_PARALLEL_WORKER_ID;nilwhen not applicable.This makes per-worker-process execution order reconstructable downstream:
PARTITION BY job_id, parallel_worker_id, parallel_worker_pid ORDER BY parallel_worker_test_indexi.e. a SQL query can reproduce any worker's
test_order-w{N}-{pid}.logfrom warehouse rows — the prerequisite for warehouse-native test-pollution / requeue-overlap analysis.How
Minitest::ParallelWorkerMetadataaccessors module prepended toMinitest::Result(fields ride the result object through Marshal/DRb).Minitest::Queue.handle_test_result— in the process that ran the test for all in-process flows.handle_test_resultruns server-side) must callMinitest::Queue.stamp_parallel_worker_metadata(result)in the worker before sending. Pre-stamped results pass through reporting untouched — otherwise the reporting-side stamp would carry the server's pid and an arrival-order index interleaved across workers.Impact on existing consumers
TestDataReporteris structurally unchanged.test_order.log, and Redis build-status/error reports are untouched.Testing
TestDataunit tests for stamped/unstamped/accessor-less results.test_test_data_reporter: worker id from env, single pid, indexes exactly0..N-1, requeued execution ordered before its final run.Also bumps the version to 0.98.0 for release.